import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public abstract class Server implements Runnable{
	
    /* Socket that clients will connect to. */
	private ServerSocket serverSocket = null;
	
    /* Thread to listen for new clients. */
	private Thread listenThread = new Thread(this);
	
    /* List of connected clients. */
	private ArrayList<Client> clients = new ArrayList<Client>();
	
	/**
	 * Creates a new server listening for clients on the given port. Clients are automatically
	 * accepted by the server and added to a client list.
	 * @param port
	 * @throws IOException
	 */
	public Server(int port)  throws IOException {
		serverSocket = new ServerSocket(port);
		
		listenThread.start();
	}
	
	/**
	 * Returns the status of the server.
	 * @return True if the server is running, false otherwise.
	 */
	public boolean isRunning() { return serverSocket != null; }
	
	/**
	 * Disconnects the given client from the server.
	 * @param client The client to disconnect.
	 */
	public void disconnect(Client client) {
		client.disconnect();
		clients.remove(client);
	}
	
	/**
	 * Stops the server. This will send TERMINATION messages to each client then
	 * will close the server socket.
	 */
	public void stop() {
		if(serverSocket != null) {
			try {
				// Disconnect the clients
				clients.forEach(client -> client.disconnect());
				clients.clear();
				
				// Close the server
				close();
			}
			catch(IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Close the server.
	 * @throws IOException If there was a problem closing the socket.
	 */
	private void close() throws IOException {
		if(serverSocket != null) {
			serverSocket.close();
			serverSocket = null;
		}
	}
	
	@Override
	public void run() {
		while(isRunning()) {
			try {
				// Accept the new client
				Socket client = serverSocket.accept();
				
				if(client != null) {
					// Notify
					Client c = clientConnected(client);
					
					clients.add(c);
					
					// Send WELCOME message
					c.sendMsg(Messages.WELCOME);
					
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * Called when a new client has connected to the server.
	 * @param client The client socket that connected.
	 */
	public abstract Client clientConnected(Socket client) throws IOException;
	
	/**
	 * Called when a client has disconnected from the server.
	 * @param client The client that disconnected.
	 */
	public abstract void clientDisconnected(Client client);
}
